#ifndef XG_SOCKET_CPP
#define XG_SOCKET_CPP
///////////////////////////////////////////////////////////////////
#include "../Socket.h"

#ifdef XG_LINUX

#include <netdb.h>

#endif

struct NetworkSetup
{
	NetworkSetup()
	{
		SocketSetup();
	}
	static NetworkSetup* Instance()
	{
		XG_DEFINE_GLOBAL_VARIABLE(NetworkSetup)
	}
};

static NetworkSetup* setup = NetworkSetup::Instance();

bool HostItem::canUse() const
{
	return host.length() > 0 && port > 0;
}
string HostItem::toString() const
{
	if (host.empty()) return host;

	return host + ":" + stdx::str(port);
}
bool HostItem::equals(const string& host) const
{
	if (host == this->host) return true;

	return Socket::IsLocalHost(host) && Socket::IsLocalHost(this->host);
}
bool HostItem::equals(const string& host, int port) const
{
	CHECK_FALSE_RETURN(port == this->port);

	if (host == this->host) return true;

	return Socket::IsLocalHost(host) && Socket::IsLocalHost(this->host);
}
sp<Socket> HostItem::getSocket(int timeout) const
{
	if (host.empty()) return NULL;

	sp<Socket> sock = newsp<Socket>();

	return sock->connect(host, port, timeout) ? sock : NULL;
}

ISocket::ISocket()
{
	sock = INVALID_SOCKET;
	closed = true;
}
ISocket::~ISocket()
{
	close();
}
bool ISocket::isClosed() const
{
	return IsSocketClosed(sock) ? true : false;
}
void ISocket::close()
{
	if (closed) SocketClose(sock);
	sock = INVALID_SOCKET;
}
int ISocket::tryCheck(int timeout, bool ckrd) const
{
#ifdef XG_LINUX
	struct epoll_event ev;
	struct epoll_event evs;
	HANDLE handle = epoll_create(1);

	if (handle < 0) return XG_SYSERR;

	memset(&ev, 0, sizeof(ev));

	if (ckrd)
	{
		ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;
	}
	else
	{
		ev.events = EPOLLOUT | EPOLLERR | EPOLLHUP;
	}

	epoll_ctl(handle, EPOLL_CTL_ADD, sock, &ev);

	if (epoll_wait(handle, &evs, 1, timeout) > 0)
	{
		Close(handle);

		if ((evs.events & EPOLLIN) || (evs.events & EPOLLOUT)) return XG_OK;

		return XG_SYSERR;
	}

	Close(handle);

	return XG_TIMEOUT;
#else
	int res;
	fd_set fd;
	struct timeval tv;

	FD_ZERO(&fd);
	FD_SET(sock, &fd);
	tv.tv_sec = timeout / 1000;
	tv.tv_usec = timeout % 1000 * 1000;

	if (ckrd)
	{
		res = select(sock + 1, &fd, NULL, NULL, &tv);
	}
	else
	{
		res = select(sock + 1, NULL, &fd, NULL, &tv);
	}

	if (res < 0) return XG_ERROR;
	if (res == 0) return XG_TIMEOUT;

	return FD_ISSET(sock, &fd) ? res : XG_FAIL;
#endif
}


Socket::Socket(SOCKET sock)
{
	this->sock = sock;
}
bool Socket::init(SOCKET sock)
{
	close();

	this->sock = sock;

	return true;
}
bool Socket::connect(const string& ip, int port, int timeout)
{
	close();

	sock = SocketConnectTimeout(ip.c_str(), port, timeout);

	return isClosed() ? false : true;
}
int Socket::peek(void* data, int size)
{
	return SocketPeek(sock, data, size);
}
int Socket::read(void* data, int size)
{
	return SocketRead(sock, data, size);
}
int Socket::read(void* data, int size, bool completed)
{
	return SocketReadEx(sock, data, size, completed);
}
int Socket::write(const void* data, int size)
{
	return SocketWrite(sock, data, size);
}
int Socket::write(const void* data, int size, bool completed)
{
	return SocketWriteEx(sock, data, size, completed);
}
bool Socket::setSendTimeout(int ms)
{
	return SocketSetSendTimeout(sock, ms) ? true : false;
}
bool Socket::setRecvTimeout(int ms)
{
	return SocketSetRecvTimeout(sock, ms) ? true : false;
}
string Socket::toString() const
{
	char address[64] = {0};
	
	return GetSocketAddress(sock, address) ?  address : "UNKNOWN_SOCKET";
}
HostItem Socket::getAddress() const
{
	HostItem res;
	char address[32];
	
	if (GetSocketAddress(sock, address))
	{
		char* str = strrchr(address, ':');

		if (str)
		{
			*str++ = 0;
			
			res.host = address;
			res.port = stdx::atoi(str);
		}
	}

	return res;
}
int Socket::writeEmptyLine()
{
	return write("\r\n", 2);
}
int Socket::readLine(char* data, int size)
{
	int val = 0;
	int readed = 0;

	while (readed < size)
	{
		if ((val = read(data + readed, size - readed, FALSE)) < 0)
		{
			return val;
		}

		if (val == 0 || (readed += val) < 2)
		{
			continue;
		}

		if (data[readed - 2] == '\r' && data[readed - 1] == '\n')
		{
			data[readed - 2] = 0;

			return readed - 2;
		}
	}

	return XG_DATAERR;
}
int Socket::writeLine(const char* data, int size)
{
	if (size + 2 > 64 * 1024)
	{
		size = write(data, size);

		return size > 0 ? write("\r\n", 2) : size;
	}
	else
	{
		char buffer[64 * 1024];

		memcpy(buffer, data, size);
		buffer[size++] = '\r';
		buffer[size++] = '\n';

		return write(buffer, size);
	}
}
bool Socket::IsLocalHost(const string& ip)
{
	vector<string> vec;

	if (ip == LOCAL_IP) return true;

	CHECK_FALSE_RETURN(GetLocalAddress(vec) > 0);

	return std::find(vec.begin(), vec.end(), ip) != vec.end();
}
bool Socket::IsHostString(const string& str)
{
	int val;
	vector<string> vec;

	CHECK_FALSE_RETURN(stdx::split(vec, str, ".") == 4);

	for (int i = 0; i < 4; i++)
	{
		const string& str = vec[i];

		CHECK_FALSE_RETURN(str.length() > 0 && str.length() < 4 && IsNumberString(str.c_str()));
		CHECK_FALSE_RETURN((val = atoi(str.c_str())) >= 0 && val <= 0xFF);
	}

	return true;
}
int Socket::GetLocalAddress(vector<string>& vec)
{
	static vector<string> res;
	static time_t utime = 0;
	time_t now = time(NULL);
	static Mutex mtx;
	Locker lk(mtx);

	if (utime + 60 < now)
	{
		char hostname[0xFF];
	
		if (gethostname(hostname, sizeof(hostname)) < 0) return XG_SYSERR;

		struct hostent* host = gethostbyname(hostname);

		if (host == NULL) return XG_SYSERR;

		res.clear();

		for (int i = 0; i < 8 && host->h_addr_list[i]; i++)
		{
			const char* ip = inet_ntoa(*(struct in_addr*)(host->h_addr_list[i]));
			
			if (ip && IsHostString(ip)) res.push_back(ip);
		}

		std::sort(res.begin(), res.end(), [](string a, string b){
			if (a == LOCAL_IP) a = 'z';
			if (b == LOCAL_IP) b = 'z';
			if (stdx::startwith(a, "192.168")) a = "x" + a;
			if (stdx::startwith(b, "192.168")) b = "x" + b;
			return a < b;
		});

		utime = now;
	}

	vec = res;

	return vec.size();
}
string Socket::GetHostAddress(const string& host)
{
	if (IsHostString(host)) return host;

	struct Item
	{
		char ip[64];
		time_t utime;
	};

	Item item;
	time_t now = time(NULL);
	static CacheMap<string, Item> hostmap;

	if (hostmap.get(host, item) && item.utime + 60 > now) return item.ip;

	memset(item.ip, 0, sizeof(item.ip));
	item.utime = ::GetHostAddress(host.c_str(), item.ip) ? now : now - 55;

	hostmap.set(host, item);

	return item.ip;
}

ServerSocket::ServerSocket()
{
}
HostItem ServerSocket::getAddress() const
{
	HostItem res;
	const char* host = inet_ntoa(addr.sin_addr);

	if (host)
	{
		res.host = host;
		res.port = ntohs(addr.sin_port);
	}

	return res;
}
bool ServerSocket::listen(const string& ip, int port, int backlog, bool reused)
{
	close();

	sock = socket(AF_INET, SOCK_STREAM, 0);

	if (isClosed()) return false;

	if (reused)
	{
		int val = 1;

		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)(&val), sizeof(val));
	}

	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip.c_str());

	return ::bind(sock, (const sockaddr*)(&addr), sizeof(addr)) == 0 && ::listen(sock, backlog) == 0;
}
SOCKET ServerSocket::accept()
{
	if (isClosed()) return INVALID_SOCKET;

	socklen_t len = sizeof(addr);

	return ::accept(sock, (sockaddr*)(&addr), &len);
}

DgramSocket::DgramSocket()
{
}
bool DgramSocket::init(SOCKET sock)
{
	close();

	this->sock = sock;

	if (isClosed())
	{
		this->sock = socket(AF_INET, SOCK_DGRAM, 0);

		return isClosed() ? false : true;
	}

	return true;
}
HostItem DgramSocket::getAddress() const
{
	HostItem res;
	const char* host = inet_ntoa(addr.sin_addr);

	if (host)
	{
		res.host = host;
		res.port = ntohs(addr.sin_port);
	}

	return res;
}
int DgramSocket::read(void* data, int size)
{
	socklen_t len = sizeof(addr);

	return recvfrom(sock, (char*)(data), size, 0, (sockaddr*)(&addr), &len);
}
int DgramSocket::write(const void* data, int size)
{
	return sendto(sock, (const char*)(data), size, 0, (const sockaddr*)(&addr), sizeof(addr));
}
bool DgramSocket::bind(const string& ip, int port)
{
	if (ip.empty() || isClosed()) return false;

	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip.c_str());

	return ::bind(sock, (sockaddr*)(&addr), sizeof(addr)) == 0;
}

SocketPool::SocketPool(const string& host, int port)
{
	this->host = host;
	this->port = port;
	this->creator = [&](){
		sp<Socket> sock = newsp<Socket>();

		if (sock->connect(this->host, this->port)) return sock;

		return sock = NULL;
	};
}

void SocketPool::Disable(sp<Socket> sock)
{
	if (sock) sock->close();
}
string SocketPool::GetKey(const string& host, int port)
{
	return host + ":" + stdx::str(port);
}
sp<Socket> SocketPool::Connect(const string& host, int port)
{
	sp<SocketPool> pool = Get(host, port);

	if (!pool)
	{
		SpinLocker lk(*GetMutex());

		if (pool = Get(host, port)) return pool->get();

		Set(host, port, pool = newsp<SocketPool>(host, port));
	}

	return pool->get();
}

SpinMutex* SocketPool::GetMutex()
{
	static SpinMutex mtx;

	return &mtx;
}
TSMap<string, sp<SocketPool>>* SocketPool::GetPoolMap()
{
	static TSMap<string, sp<SocketPool>> poolmap;

	return &poolmap;
}
sp<SocketPool> SocketPool::Get(const string& host, int port)
{
	sp<SocketPool> pool;

	GetPoolMap()->get(GetKey(host, port), pool);

	return pool;
}
sp<SocketPool> SocketPool::Set(const string& host, int port, sp<SocketPool> pool)
{
	GetPoolMap()->set(GetKey(host, port), pool);

	return pool;
}
///////////////////////////////////////////////////////////////////
#endif
